package com.xpec.c4.game.login;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ms.player.Player;
import com.ms.service.error.ErrorCode;
import com.ms.service.module.account.Account;
import com.ms.service.module.account.db.AccountDAO;
import com.ms.service.module.account.db.CooperatorInfoField;
import com.ms.service.module.account.message.SSessionCloseNotify;
import com.ms.service.module.broadcastlistener.LoginLogoutListener;
import com.ms.service.telnet.STelnetCmd;
import com.xpec.c4.db.log.LogCreator;
import com.xpec.c4.db.model.log.DBAccountLoginLog;
import com.xpec.c4.game.login.async.AccountLoginRequestGetAccountData;
import com.xpec.c4.game.login.async.CharacterLoginRequestGetPlayer;
import com.xpec.c4.game.login.async2.AccountLoginGetAccountAsync;
import com.xpec.c4.game.login.async2.CharacterListGetAccountAsync;
import com.xpec.c4.game.login.async2.CharacterLoginGetPlayerAsync;
import com.xpec.c4.game.login.db.LoginDAO;
import com.xpec.c4.game.system.InitData;
import com.xpec.c4.game.time.TimeSys;
import com.xpec.c4.game.utility.GUID;
import com.xpec.c4.game.utility.GUIDFactory;
import com.xpec.c4.game.utility.GameUtility;
import com.xpec.c4.message.ConstantCode;
import com.xpec.c4.message.MessageTypes;
import com.xpec.c4.message.character.SCheckPlayerLoginLockInfo;
import com.xpec.c4.message.login.CUpdateLastLockPlayerAndDeviceTokenRequest;
import com.xpec.c4.message.login.SAccountLoginRequest;
import com.xpec.c4.message.login.SAccountLoginTokenInfo;
import com.xpec.c4.message.login.SUpdateLastLockPlayerAndDeviceTokenResponse;
import com.xpec.c4.service.contexts.Contexts;
import com.xpec.c4.service.contexts.SessionContext;
import com.xpec.c4.service.h5server.H5ServerOperations;
import com.xpec.c4.service.message.MessageAction;
import com.xpec.c4.service.module.BasicServiceModule;
import com.xpec.c4.service.module.ModuleName;
import com.xpec.c4.service.module.ServerProperties;
import com.xpec.c4.service.module.common.PlayerCommonInfoManager;
import com.xpec.c4.service.module.task.TaskTimer;
import com.xpec.c4.service.server.CloseType;
import com.xpec.c4.service.server.ServerOperations;

/**
 * 
 * 
 * @author lipeilin
 * @2011-3-9 下午04:29:32
 * 
 */
public class LoginModule extends BasicServiceModule {

	/**
	 * 
	 */
	private static final Log log = LogFactory.getLog(LoginModule.class);

	private static final int MAXCHARACTERCOUNT = 10;

	public static final int MAX_PLAYERNAME_NUMBER = 4;

	public static final int MAX_PLAYERNAME_NUMBER_TOTAL = 9999;
	
	public static final String LOGIN_TRACE_LOG_STRING = "LoginTraceLog";

	/**
	 * 暫時存放玩家資料的快取
	 */
	private HashMap<GUID, Player> playerCacheMap = new HashMap<GUID, Player>();

	public LoginModule() {
		//use recorded queue
		super(true);
	}
	
	/**
	 * 玩家登入鎖, 一個帳號同一個時間只允許一個玩家登入, 
	 * key: player ID
	 * value: 使用的player
	 */
	private Map<GUID,PlayerLockInfo> playerLoginLockMap = new HashMap<GUID, PlayerLockInfo>();
	
	/**
	 * 玩家管理
	 */
	private PlayerDataAgentLogin playerDataAgent = new PlayerDataAgentLogin(this);
	
	/**
	 * 帳號重新登入狀態記錄管理器
	 */
	private ReconnectionManager reloginManager = new ReconnectionManager();
	
	private ExecutorService openApiExecutor  = Executors.newFixedThreadPool(2);
	
	/**
	 * 管理同一帳號下登入角色數量的管理器
	 */
	private PlayerLoginManager playerLoginManager = new PlayerLoginManager();
	
	/**
	 * 創角時用來暫存資料的資料序號
	 */
	private AtomicInteger createCharacterDataSn = new AtomicInteger(0);
	
	/**
	 * 創角時暫存資料用的 map<br>
	 * key:資料序號<br>
	 * value:AccountData
	 */
	private HashMap<Integer, Account> createCharacterDataMap = new HashMap<Integer, Account>();
	
	@Override
	public void init() {

		super.init();

		// 注册相关的class给Action使用
		registerActionClass(LoginAction.class);
		registerActionClass(LoginVerifyAction.class);

		// ...
		InitData.initAll();	
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.xpec.c4.service.module.IServiceModule#start()
	 */
	@Override
	public boolean startup() {
		registerJmxInfo();
		CheckReconnectInfoExpireTask checkTask = new CheckReconnectInfoExpireTask();
		this.addTaskTimer(checkTask, TaskTimer.LOOP,
				ReconnectionManager.MODULE_CHECK_PERIOD,
				ReconnectionManager.MODULE_CHECK_PERIOD, this);
		
		if(this.isFirstModule()){
			LoginLogoutListener.clearAllNotifyModulesInRedis();
		}
		
		return super.startup();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.xpec.c4.service.module.IServiceModule#stop()
	 */
	@Override
	public void shutdown() {
		super.shutdown();
	}

	/**
	 * 处理兼容原本metadata @out 的效果
	 * 
	 * @param sessionID
	 * @param account
	 */
	private void writebackAccountToContexts(GUID sessionID, Account account) {

		// TODO 这边应该还有问题
		// 没这个session要建新的

		SessionContext context = Contexts
				.getSessionContextsBySessionID(sessionID);

		if (context != null) {

			// 把account 写回Contexts
			// 手动写回
			context.set("sessionID", sessionID);
			// context.set("account", account);
			context.set("accountID", account.getAccountId());
		} else {
			log.error("[0-3]session context is null! - sessionID:" + sessionID
					+ " Account:("
					+ account.getAccountId() + ")");
		}

	}

	/**
	 * 斷線, 先傳送錯誤訊息再斷線, 只能用在 account 登入流程中
	 * 
	 * @param sessionID
	 * @param errorCode
	 * @param closeType
	 */
	public void sessionClose(GUID sessionID, String reason, int errorCode, CloseType closeType) {
		LoginSender.sendAccountMessage(sessionID, errorCode);

		String gatewayId = PlayerCommonInfoManager.getInstance().getGatewayBySessionId(sessionID);
		if (ServerProperties.isTargetModule(gatewayId, ModuleName.H5GATEWAY)) {
			H5ServerOperations.sessionClose(sessionID, closeType, this);
		} else {
			ServerOperations.sessionClose(sessionID, closeType, this);
		}

		if (log.isErrorEnabled()) {
			log.error(reason + ", session close, sessionID=" + sessionID + " errorCode="
					+ ConstantCode.getName(ErrorCode.class, errorCode) + " closeType=" + closeType.name());
		}
	}

	/**
	 * 
	 * @param message
	 * @param sessionID
	 */
	public void accountLoginRequest(SAccountLoginRequest message, GUID sessionID) {

		if (log.isInfoEnabled()) {
			log.info("[0]accountLoginRequest: accountID:" + message.account + " sessionID:" + sessionID);
		}

		//判斷是否重新連線
		boolean isReconnect = false;
		if (message.account == null) {
			if (message.reconnectToken != null) {
				message.account = reloginManager.verifyReconnectLoginInfo( message.reconnectToken, message.code);
				isReconnect = true;
			}
			if (message.account == null) {
				SAccountLoginTokenInfo token = new SAccountLoginTokenInfo();
				token.setErrorCode(ErrorCode.ACCOUNT_FAILURE_ACCOUNT_ERROR);
				
				LoginSender.sendAccountLoginToken(sessionID, token);
				LoginSender.sendAccountMessage(sessionID, ErrorCode.ACCOUNT_FAILURE_ACCOUNT_ERROR);
				if (ServerProperties.isTargetModule(PlayerCommonInfoManager.getInstance().getGatewayBySessionId(sessionID), ModuleName.H5GATEWAY)) {
					H5ServerOperations.sessionClose(sessionID, CloseType.KICK_BY_ERROR, this);
				} else {
					ServerOperations.sessionClose(sessionID, CloseType.KICK_BY_ERROR, this);
				}
				return;
			} else {
				log.error("[0-1]accountLoginRequest account f5 reconnect !!, accountID:"
						+ message.account + " sessionID:" + sessionID);
			}
			if(log.isInfoEnabled()){
				log.info(LoginModule.LOGIN_TRACE_LOG_STRING+" a: "+message.account+" judge if reconnect");
			}
		}

		// 取出對應的 coop id
		String coopId = AccountDAO.getCooperatorAccountIdFromRedis(message.account);
		if (!GameUtility.isHaveString(coopId)) {
			sessionClose(sessionID, "get coopId fail, accountId=" + message.account,
					ErrorCode.LOGIN_GET_COOP_INFO_DATA_FAIL, CloseType.KICK_BY_ERROR);
			return;
		}

		// 同一帳號之前的連線 id
		String prevSessionId = AccountDAO.getCoopInfoField(coopId, CooperatorInfoField.SESSION_ID);

		// 檢查重複登入
		if (!isReconnect) { // 不是斷線重連
			// 同一帳號已經有連線存在
			if (GameUtility.isHaveString(prevSessionId)) {
				// 把目前的自己斷掉
				sessionClose(sessionID, "prev connection already exist, accountId=" + message.account,
						ErrorCode.LOGIN_GET_COOP_INFO_DATA_FAIL, CloseType.KICK_BY_CANT_RELOGIN);
				return;
			}
		} else { // 斷線重連
			// 之前的連線還存在
			if (GameUtility.isHaveString(prevSessionId)) {
				String gatewayId = PlayerCommonInfoManager.getInstance().getGatewayBySessionId(sessionID);
				if (ServerProperties.isTargetModule(gatewayId, ModuleName.H5GATEWAY)) {
					H5ServerOperations.sessionClose(sessionID, CloseType.KICK_BY_RECONNECT_KICK_PREV, this);
				} else {
					ServerOperations.sessionClose(sessionID, CloseType.KICK_BY_RECONNECT_KICK_PREV, this);
				}
				
				if (log.isErrorEnabled()) {
					log.error("account " + message.account + " reconnect, prev session " + prevSessionId
							+ " exist, kick it.");
				}
			}
		} // if (!isReconnect) {

		// 更新連線 id
		boolean res = AccountDAO.saveCoopInfoField(coopId, CooperatorInfoField.SESSION_ID, sessionID.toString());
		if (!res) {
			sessionClose(sessionID, "save session id fail, accountId=" + message.account,
					ErrorCode.DB_OPERATION_FAIL, CloseType.KICK_BY_ERROR);
			return;
		}

		// 檢查帳號下連線數量是否超過
		if (playerLoginManager.isAccountSessionFull(message.account)) {
			// 踢掉第一條連線
			GUID firstSessionId = playerLoginManager.getAccountFirstSession(message.account);
			if (firstSessionId != null) {
				LoginSender.sendAccountMessage(firstSessionId, ErrorCode.ACCOUNT_SESSION_COUNT_REACH_MAX);
				if (ServerProperties.isTargetModule(PlayerCommonInfoManager.getInstance().getGatewayBySessionId(sessionID), ModuleName.H5GATEWAY)) {
					H5ServerOperations.sessionClose(firstSessionId, CloseType.KICK_BY_ACCOUNT_SESSION_FULL, this);
				} else {
					ServerOperations.sessionClose(firstSessionId, CloseType.KICK_BY_ACCOUNT_SESSION_FULL, this);
				}
				playerLoginManager.removeSession(firstSessionId);
				
				LoginLogoutListener.removeLogOutSessionListener(this, firstSessionId);

				if (log.isErrorEnabled()) {
					int count = playerLoginManager.getAccountSessionCount(message.account);
					int max = playerLoginManager.getMaxConnectSession();
					log.error("account " + message.account + " session full, kick out first session "
							+ firstSessionId + " (" + count + "/" + max + ")");
				}
			}
		}
		
		//先更新一下帳號登入資訊
		String randomToken = GUIDFactory.genUniqueID();
		if(log.isInfoEnabled()){
			log.info(LoginModule.LOGIN_TRACE_LOG_STRING+" a: "+message.account+" Generate new token: "+randomToken);
		}
		reloginManager.updateAccountLastLoginInfo(message.account, message.code, sessionID, null, randomToken,
				message.reconnectToken);
		SAccountLoginTokenInfo tokenInfo =  new SAccountLoginTokenInfo(randomToken, isReconnect, ReconnectionManager.EXPIRE_PERIOD);
		

	    //如果处于黑名单中，禁止登录
		// FIXME 這貌似是一張global的Table, 在更新的時候, 別的線程也會讀取吧！
		//已对BlackListManager的update函数加锁
		if (BlackListManager.getBlackListMap().containsKey(message.account)) {

			// 检查黑名单是否超时过期
			long endtime = BlackListManager.getBlackListMap().get(
					message.account);
			//如果黑名单未过期 或者 已被加入永久的黑名单
			if ((endtime != 0 && endtime > TimeSys.currentTimeMills())||(endtime == 0)) {
				LoginSender.sendAccountMessage(sessionID,
						ErrorCode.ACCOUNT_FAILURE_BLACK_LIST_ERROR);
				log.error("[0-2]------forbiden logining,the account "
						+ message.account + " is in the black list!!------");
				return;
			}
			//解除黑名单（黑名单截止日期已经过期）
			else{
				BlackListManager.removeBlackList(message.account);
				log.error("[0-3]account " + message.account
						+ " was removed from BLACKLIST!");
			}
		}

		AccountLoginRequestGetAccountData data = new AccountLoginRequestGetAccountData();
		data.accountID = message.account;
		data.sessionID = sessionID;
		data.playerIp = message.playerIp;
		data.loginToken  = tokenInfo;

		AccountLoginGetAccountAsync task = new AccountLoginGetAccountAsync();
		task.initial(message.account);
		task.setData(data);
		
		addAsyncCommand(task);
	}

	/**
	 * 
	 */
	public void accountLoginRequestGetAccountResultCallback(
			AccountLoginRequestGetAccountData data) {

		// 密码验证

		data.account.online();
		data.account.setClientAddress(data.playerIp);
		
		//f5重登以下資訊不會變
		if(!data.loginToken.isReconnect())
		{
			
		}
		
		//配合client端需求, 不馬上回傳角色列表, 等他那邊loading結束才會跟server要角色列表(新創腳就不會要了)
		writebackAccountToContexts(data.sessionID, data.account);

		if(log.isInfoEnabled()){
			log.info(LoginModule.LOGIN_TRACE_LOG_STRING+" method: "+GameUtility.getCurMethodName()+" /s: "+data.sessionID+" /a: "+data.accountID);
		}

		data.loginToken.setErrorCode(ErrorCode.SUCCESS);
		LoginSender.sendAccountLoginToken(data.sessionID, data.loginToken);
		if (log.isInfoEnabled()) {
			log.info(LoginModule.LOGIN_TRACE_LOG_STRING + " send account login token to client, timeZoneOffset "
					+ data.loginToken.getTimeZoneOffset() + " " + data.loginToken.getTimeZoneOffsetString()
					+ ", s:" + data.sessionID + " a:" + data.accountID);
		}
		
		// 連線成功, 加入一條連線
		playerLoginManager.addSession(data.sessionID, data.accountID, null);
		
		LoginLogoutListener.addLogOutSessionListener(this, data.sessionID);
		
		// account login log
		// TODO kevin jb verify
		DBAccountLoginLog acclog = LogCreator.createLog(DBAccountLoginLog.class);
		acclog.setAccountId(GUID.toString(data.account.getAccountId()));
		acclog.setCooperatorAccountId(data.account.getCooperatorAccountId());
		acclog.setClientIP(data.playerIp);
		acclog.setVenderId(data.account.getVenderId());
		acclog.setSubVenderId(data.account.getSubVenderId());
		Contexts.getModule().saveDbLog(acclog);
//		LogModelPool.add(acclog);

		if (log.isInfoEnabled()) {
			log.info(LoginModule.LOGIN_TRACE_LOG_STRING + " write account login log, s:" + data.sessionID + " a:"
					+ data.accountID + " coop:" + data.account.getCooperatorAccountId() + " ip:" + data.playerIp);
		}
	}
	
	/**
	 * 獲取角色列表
	 * 
	 * @param account
	 * @param sessionID
	 */
	public void characterListRequest(GUID accountID, GUID sessionID) {
		if (isWaitToClose()) {
			LoginSender.sendAccountMessage(sessionID, ErrorCode.GAME_SERVER_WAIT_TO_CLOSE);
			return;
		}

		CharacterListGetAccountAsync task = new CharacterListGetAccountAsync();
		task.initial(accountID);
		task.setSessionID(sessionID);
		
		addAsyncCommand(task);
	}
	
	/**
	 * 
	 * @param playerGuid
	 * @param accountID
	 * @param sessionID
	 * @param requestType
	 */
	public Account characterBeforeLoginCheckProc(Account account, GUID playerGuid,
			GUID accountID, GUID sessionID, String reconntoken, char requestType) {
		if(account == null){
			if (log.isErrorEnabled()) {
				log.error("characterBeforeLoginCheckProc return because local account data = null : " + accountID);
			}
			return null;
		}

		// 檢查指定角色是否在目前帳號下
		if (!account.getPlayerSerialNumMap().containsKey(playerGuid)) {
			LoginSender.sendAccountMessage(sessionID, ErrorCode.ACCOUNT_CHARACTER_NOT_EXIST);
			if (log.isErrorEnabled()) {
				log.error("character " + playerGuid + " is not exist in account " + account.getAccountId() + " | "
						+ account.getCooperatorAccountId());
			}
			return null;
		}

		//檢查玩家lock狀態<br>
		//檢查是否已無其他角色登入, 即可登入<br>
		//TODO:
		PlayerLockInfo lastLoginId = playerLoginLockMap.get(playerGuid);
		
		if(lastLoginId != null &&  lastLoginId.getLockPlayerGuid() != null && lastLoginId.getSessionID() != null){
			boolean isH5User = ServerProperties.isTargetModule(PlayerCommonInfoManager.getInstance().getGatewayBySessionId(sessionID), ModuleName.H5GATEWAY);
			
			//如果過時踢前一個人，沒過時踢自己
			if(lastLoginId.isExpire()){
				//過時踢前一個 ,並且從session list中移除
				removePlayerLockMap(lastLoginId.getSessionID(), playerGuid, account);
				
				// 使用 h5 登入, 除了踢掉前一條連線, 也把目前的自己踢掉, 強迫再重登一次
				GUID pid = PlayerCommonInfoManager.getInstance().getPlayerGuidBySession(lastLoginId.getSessionID());
				if (pid != null) { // 如果上一個連線真的存在, 才把目前的連線也踢掉, 避免一直觸發
					if (isH5User) {
						// H5ServerOperations.sessionClose(sessionID, CloseType.KICK_BY_RELOGIN_SELF, this);
						
						// 通知 client 被斷線
						MessageAction.sendMessage(sessionID, MessageTypes.S_ACCOUNT_CHARACTER_KICK_BY_SERVER, CloseType.KICK_BY_RELOGIN_SELF);

						// 等一段時間執行斷線
						if (log.isInfoEnabled()) {
							log.info("register KickReloginSelfTaskTimer");
						}
						
						KickReloginSelfTaskTimer kickReloginSelfTimer = new KickReloginSelfTaskTimer();
						kickReloginSelfTimer.setSessionID(sessionID);
						kickReloginSelfTimer.setCloseType(CloseType.KICK_BY_RELOGIN_SELF);
						kickReloginSelfTimer.setOperateModule(this);
						
						kickReloginSelfTimer.setTimerOption(TaskTimer.ONCE);
						kickReloginSelfTimer.setInitialDelay(0);
						kickReloginSelfTimer.setPeriod(100);
						
						addTaskTimer(kickReloginSelfTimer);
						return null;
					}
				}
			}else{
				//沒過時踢自己
				if (isH5User) {
					H5ServerOperations.sessionClose(sessionID, CloseType.KICK_BY_RELOGIN, this);
				} else {
					ServerOperations.sessionClose(sessionID, CloseType.KICK_BY_RELOGIN, this);
				}
				return null;
			}
		}
		
		//更新重連token對應playerID
		reloginManager.updateAccountLastLoginPlayerGuid(accountID, playerGuid, reconntoken);
		
		//將玩家放入lock table , 之後就不讓別的玩家登入
		addPlayerLockMap(sessionID, playerGuid, System.currentTimeMillis());
		
		//沒有人占用，直接抓資料
		if (requestType == SCheckPlayerLoginLockInfo.REQUEST_TYPE_CHARACTER_LOGIN) {
			characterLoginRequest(playerGuid, account, sessionID);
		}
		if(log.isInfoEnabled()){
			log.info(LoginModule.LOGIN_TRACE_LOG_STRING+" Check lock finish, a: "+accountID.toString()+"/ g: "+playerGuid);
		}
		return account;
	}
	
	
	/**
	 * 產生角色姓名後隨機碼(字串)
	 *  0000 ~ 9999 (排除數字4) 
	 */
	public static String getPlayerNameRandomNumber(){
		int i;
		String numberStr = "";
		for(i = 0 ; i < MAX_PLAYERNAME_NUMBER ; i++){
			int number = (int)(Math.random() * 10);
			while(number == 4){
				number = (int)(Math.random() * 10);
			}
			numberStr += number;
		}
		return numberStr;
	}
	
	/**
	 * 產生並檢查角色姓名隨機碼 
	 */
	public static String createAndCheckNameNumber(String name){
		//設定每次產生幾比隨機碼去比對(第一次3筆、不成功再5筆)
		int[] range = new int[]{3,5};
		int i , k;
		String playerNameNumber = null;
		List<String> nameStrList = new ArrayList<String>();
		for(i = 0 ; i < range.length ; i++){
			System.out.println("random level" + (i+1) + " , range --> " + range[i]);
			// 產生姓名隨機碼(字串)
//			nameStrList.add("5377");
//			nameStrList.add("2102");
//			nameStrList.add("0783");
			for(k = 0 ; k < range[i] ; k++){
				String nameNumber = getPlayerNameRandomNumber();
				nameStrList.add(nameNumber);
				System.out.println("random number ---> " + nameNumber);
			}
			// 筆對DB同名且同編碼清單
			String checkString = LoginDAO.checkRepeatPlayerName(name, nameStrList);
			for(String str:nameStrList){
				if(!checkString.contains(str)){
					playerNameNumber = str;
					System.out.println( playerNameNumber + " <---> " + str);
					break;
				}
			}
			
			if(playerNameNumber != null && !(playerNameNumber.isEmpty()) ){
				break;
			}else{
				nameStrList.clear();
			}
		}
		return playerNameNumber;
	}
	
	/**？
	 * 踢除現在session連線，和移除lock
	 */
	private void removePlayerLockMap(GUID sessionID,GUID playerID,Account account){
		if (ServerProperties.isTargetModule(PlayerCommonInfoManager.getInstance().getGatewayBySessionId(sessionID), ModuleName.H5GATEWAY)) {
			H5ServerOperations.sessionClose(sessionID, CloseType.KICK_BY_RELOGIN, this);
		} else {
			ServerOperations.sessionClose(sessionID, CloseType.KICK_BY_RELOGIN, this);
		}
		playerLoginLockMap.remove(playerID);
	}
	
	/**
	 * 成功進入鎖住此player
	 */
	private void addPlayerLockMap(GUID sessionID, GUID playerID ,Long timeOut){
		//將玩家放入lock table , 之後就不讓別的玩家登入
		playerLoginLockMap.put(playerID, new PlayerLockInfo(playerID, timeOut , sessionID));
	}
	
	/**
	 * 
	 * @param playerGuid
	 * @param account
	 * @param sessionID
	 */
	public void characterLoginRequest(GUID playerGuid, Account account, GUID sessionID) {
		
		CharacterLoginRequestGetPlayer data = new CharacterLoginRequestGetPlayer();
		data.callback = this;
		data.playerGuid = playerGuid;
		data.account = account;
		data.sessionID = sessionID;
		
		// TODO kevin jb verify
		CharacterLoginGetPlayerAsync async = new CharacterLoginGetPlayerAsync();
		async.initial(playerGuid, sessionID);
		async.setData(data);
		
		// login module 才特別把 key 設定成 account id , login module 上同一帳號的帳號和角色都走同樣的
		// async queue load 資料.
		async.key = account.getAccountId();
		
		addAsyncCommand(async);
	}
	
	public void characterLoginRequestGetPlayerCallback(CharacterLoginRequestGetPlayer data) {
		if (data.player != null) {
			playerCacheMap.put(data.playerGuid, data.player);
		}
		
		if (data.result) {
			characterLogin(data.account, data.player, data.sessionID , data.ownerModule);
		}
		else{
			log.error("characterLoginRequestGetPlayerCallback get player data result false : " + data.playerGuid);
		}
	}
	
	/**
	 * 角色登录函数<br>
	 * 这里不能修改角色数据. 要修改,请到Player.loginServer()
	 * 
	 * @param guid
	 * 
	 * @2011-12-9 下午05:20:55
	 */
	private void characterLogin(Account account, Player player, GUID sessionID , String ownerModule) {

		if (player == null) {
			LoginSender.sendAccountMessage(sessionID,
					ErrorCode.ACCOUNT_CHARACTER_NOT_EXIST);
			log.error("characterLogin but player == null , return  session:" + sessionID);
			return;
		}
		
		CharacterLoginProcLogic.characterLoginCreateCSProc(account, player, sessionID,this);
	}
	
	public void accountLoginRequestLoadPlayerCallback(AccountLoginRequestGetAccountData data){
		LoginSender.sendCharacterList(data.sessionID, data.msg);
		if(log.isInfoEnabled()){
			log.info(LoginModule.LOGIN_TRACE_LOG_STRING+" send PlayerList to client, s: "+data.sessionID.toString()+" /a: "+data.accountID.toString());
		}
	}

	public PlayerDataAgentLogin getPlayerDataAgent() {
		return playerDataAgent;
	}
	
	/**
	 * 排成定期清理，過期重連token
	 */
	public void onChechReconnectInfoExpire(){
		this.reloginManager.expireCheck();
	}
	
	/**
	 * 
	 */
	public void characterLoginCallback(CharacterLogin data) {

		if(log.isInfoEnabled() && ServerProperties.isSaveConsoleLog(data.player.getUserType())){
			log.info(LoginModule.LOGIN_TRACE_LOG_STRING+" send [S_ACCOUNT_CHARACTER_LOGIN_RESPONSE], s: "+data.sessionID.toString()+" /g: "+data.player.getGuid().toString());
		}
		LoginSender.sendCharactorLoginResponse(data.sessionID, data.responseMsg);
		
		//將玩家放入lock table , 之後就不讓別的玩家登入
		addPlayerLockMap(data.sessionID , data.player.getGuid(), null);
		
		// 選擇角色後, 加入角色
		playerLoginManager.addSession(data.sessionID, data.player.getAccountId(), data.player.getGuid());
		if(log.isInfoEnabled() && ServerProperties.isSaveConsoleLog(data.player.getUserType())){
			log.info(LoginModule.LOGIN_TRACE_LOG_STRING+" put player to lock table, g: "+data.player.getGuid().toString());
		}
	}
	
	/**
	 * 更新帳號內的鎖定角色與deviceToken請求
	 */
	public void updateLockPlayerAndDeviceToken(CUpdateLastLockPlayerAndDeviceTokenRequest data, GUID sessionID, GUID accountID, Account account){
		SUpdateLastLockPlayerAndDeviceTokenResponse response = new SUpdateLastLockPlayerAndDeviceTokenResponse();

		//檢查一下消息內容
		if(data.getLastLockPlayerGUID() == null || data.getCurrentLockPlayerGUID() == null){
			response.setErrorCode(ErrorCode.INVALID_PARAM);
			MessageAction.sendMessage(sessionID, MessageTypes.S_UPDATA_LOCK_PLAYER_AND_DEVICE_TOKEN_RESPONSE, response);
			return;
		}

		if(account == null){
			response.setErrorCode(ErrorCode.NO_ACCOUNT_DATA);
			MessageAction.sendMessage(sessionID, MessageTypes.S_UPDATA_LOCK_PLAYER_AND_DEVICE_TOKEN_RESPONSE, response);
			return;
		}
		
		//如果傳來上次的不一樣
		if(!GameUtility.safeStringEquals(account.getLastLockPlayerGUID(), data.getLastLockPlayerGUID().toString())){
			//而且又不是第一次傳(第一次傳這兩個GUID會一樣)
			if(!data.getCurrentLockPlayerGUID().equals(data.getLastLockPlayerGUID())){
				response.setErrorCode(ErrorCode.INVALID_PARAM);
				MessageAction.sendMessage(sessionID, MessageTypes.S_UPDATA_LOCK_PLAYER_AND_DEVICE_TOKEN_RESPONSE, response);
				return;
			}
		}
		
		ArrayList<GUID> players = account.getPlayers();
		if(players == null || players.size() == 0){
			response.setErrorCode(ErrorCode.NO_PLAYER_DATA);
			MessageAction.sendMessage(sessionID, MessageTypes.S_UPDATA_LOCK_PLAYER_AND_DEVICE_TOKEN_RESPONSE, response);
			return;
		}
		
		if(players.contains(data.getCurrentLockPlayerGUID()) && players.contains(data.getLastLockPlayerGUID())){
			String deviceToken = data.getDeviceToken();
			
			//設定deviceToken:
			String deviceTokenMD5 = null;
		}
		else{
			response.setErrorCode(ErrorCode.ACCOUNT_CHARACTER_NOT_EXIST);
			MessageAction.sendMessage(sessionID, MessageTypes.S_UPDATA_LOCK_PLAYER_AND_DEVICE_TOKEN_RESPONSE, response);
			return;
		}
	}

	@Override
	public void procTelnetCommand(STelnetCmd obj) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void procThrowException(Object obj) {
		// TODO Auto-generated method stub
		
	}
	
	public void recvSessionCloseNotify(SSessionCloseNotify notify) {
		// 斷線, 將 session 資料移除
		playerLoginManager.removeSession(notify.getSessionId());
		reloginManager.onPlayerDisconnect(notify);
	}

	public PlayerLoginManager getPlayerLoginManager() {
		return playerLoginManager;
	}

	public HashMap<Integer, Account> getCreateCharacterDataMap() {
		return createCharacterDataMap;
	}
	
	/**
	 * 從快取中取得玩家資料
	 * 
	 * @param playerId
	 * @return
	 */
	public Player getPlayerFromCache(GUID playerId) {
		if (playerId == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() fail, player = null.");
			}
			return null;
		}

		Player player = playerCacheMap.get(playerId);

		return player;
	}
	
	/**
	 * 從快取中把玩家資料刪除
	 * 
	 * @param playerId
	 */
	public void removePlayerFromCache(GUID playerId) {
		if (playerId == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() fail, player = null.");
			}
			return;
		}

		if (playerCacheMap.containsKey(playerId)) {
			playerCacheMap.remove(playerId);
		}
	}
}